//Note:
//This port is based on PCAUSA's Winsock port of the original Unix Test TCP.
//TTCP was originally released to the "Public Domain" by its authors. In order
//to make this particular port available to the public with reasonable effort, 
//I've chosen to license this Windows CE/Windows Compact port under the MIT license. 
//The reason for this is simply because few of the free hosting services on 
//"ye'ol' internet" seem to endorse "Public Domain" projects, and the MIT license 
//seems to be the closest fit available.

#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <TChar.h>

#include "getopt.h" 
#include "resolve.h"

/*
 *  T T C P . C
 *
 * Test TCP connection.  Makes a connection on port 5001
 * and transfers fabricated buffers or data copied from stdin.
 *
 * Usable on 4.2, 4.3, and 4.1a systems by defining one of
 * BSD42 BSD43 (BSD41a)
 * Machines using System V with BSD sockets should define SYSV.
 *
 * Modified for operation under 4.2BSD, 18 Dec 84
 *      T.C. Slattery, USNA
 * Minor improvements, Mike Muuss and Terry Slattery, 16-Oct-85.
 * Modified in 1989 at Silicon Graphics, Inc.
 *  catch SIGPIPE to be able to print stats when receiver has died 
 *  for tcp, don't look for sentinel during reads to allow small transfers
 *  increased default buffer size to 8K, g_nNumBuffersToSend to 2K to transfer 16MB
 *  moved default port to 5001, beyond IPPORT_USERRESERVED
 *  make sinkmode default because it is more popular, 
 *      -s now means don't sink/source.
 *       in sink/source mode use pattern generator to fill send
 *       buffer (once at init time). received data is tossed.
 *  count number of read/write system calls to see effects of 
 *      blocking from full socket buffers
 *  for tcp, -D option turns off buffered writes (sets TCP_NODELAY sockopt)
 *  buffer alignment options, -A and -O
 *  print stats in a format that's a bit easier to use with grep & awk
 *  for SYSV, mimic BSD routines to use most of the existing timing code
 * Modified by Steve Miller of the University of Maryland, College Park
 *  -b sets the socket buffer size (SO_SNDBUF/SO_RCVBUF)
 * Modified Sept. 1989 at Silicon Graphics, Inc.
 *  restored -s sense at request of tcs@brl
 * Modified Oct. 1991 at Silicon Graphics, Inc.
 *  use getopt(3) for option processing, add -f and -T options.
 *  SGI IRIX 3.3 and 4.0 releases don't need #define SYSV.
 *
 * PCAUSA Version 1.00.00.01 - Modified April, 1999 at Printing
 * Communications Assoc., Inc. (PCAUSA) PCAUSA. Initial port to Winsock.
 *
 * PCAUSA Version 1.00.00.02 - Modified January, 2000 at Printing
 * Communications Assoc., Inc. (PCAUSA) to fix setting of setsockopt call
 * for TCP_NODELAY.
 *
 * PCAUSA Version 2.01.01.04 - Modified May, 2002 at Printing
 * Communications Assoc., Inc. (PCAUSA). Incorporates substantial rework
 * suit the author's style and purpose (as a sockets programming learning
 * program and a TCP/IP exploration tool).
 *
 * Although the functionality has been extended, the application is
 * backwards-compatible with previous PCAUSA releases.
 *
 * PCAUSA Version 2.01.01.05 - Modified May, 2002 at PCAUSA. Modification
 * to allow SO_RVCBUF and SO_SNDBUF values of zero(0) to be handled.
 *
 * PCAUSA Version 2.01.01.06 - Modified April, 2003 at PCAUSA. Fixed minor
 * bug in TTCP transmitter. PCAUSA PCATTCP preamble was not inserted
 * correctly when building transmit buffer.
 * 
 * PCAUSA Version 2.01.01.07 - Modified November, 2003 at PCAUSA.
 * Incorporated fix identified by Clarkson University that reduces
 * hangs when ending the UDP transmitter test. See additional comments
 * in TTCP_TransmitUDP module.
 * 
 * PCAUSA Version 2.01.01.08 - Modified March, 2005 at PCAUSA.
 * Added -L Data rate limiter option for transmitters.
 * 
 * PCAUSA Version 2.01.01.09 - Additional modifications March, 2005
 * at PCAUSA.
 * 
 * PCAUSA Version 2.01.01.10 - Additional modifications November, 2009
 * at PCAUSA. Added -a to bind to local host IP address.
 * 
 * PCAUSA Version 2.01.01.11, February 4, 2010 Now flush stderr/stdout
 * for all exit paths..
 * 
 * PCAUSA Version 2.01.01.12, April 29, 2010 Now set periodic timer
 * resolution using timeBeginPeriod and timeEndPeriod.
 *
 * Timestamp fetched using GetSystemTimeAsFiletime, which possibly
 * improves resolution.
 *
 * Small change in sending UDP guard messages with goal of improving
 * elapsed time measurement.
 *
 * Now raise thread priority to THREAD_PRIORITY_ABOVE_NORMAL.
 * 
 * PCAUSA Version 2.01.01.13, August 24, 2010 Adapted the tool to
 * support IPv6.
 *
 * Fixed bug in multi-port -p option. This option created one too
 * few port instances.
 *
 * Revised output to include more IP version, address and port
 * information.
 *
 * PCAUSA Version 2.01.01.14, August 25, 2010 Removed code that
 * raised thread priority to THREAD_PRIORITY_ABOVE_NORMAL. This
 * may introduce unexplained behavior with testing.
 *
 * Distribution Status -
 *      Public Domain.  Distribution Unlimited.
 */

#define  PCATTCP_VERSION   "2.01.01.14"

/////////////////////////////////////////////////////////////////////////////
//// GLOBAL VARIABLES
//

BOOL     g_bShutdown = FALSE;               // Set by Ctrl-C Handler
WSADATA  g_WsaData;

#define MAX_PORTS 64

#ifndef IPPORT_TTCP
#define IPPORT_TTCP          5001
#endif

ADDRESS_FAMILY    g_SockAddrFamily = AF_INET;   // Default

//
// Test Command/Configuration Data
//
typedef
struct _CMD_BLOCK
{
   BOOLEAN     m_bTransmit;
   USHORT      m_Protocol;

   SOCKADDR_STORAGE  m_RemoteAddr;
   int         m_nRemoteAddrLen;

   SOCKADDR_STORAGE m_LocalAddr;      // Host Byte Order
   int         m_nLocalAddrLen;

   USHORT      m_Port;              // First Port. Host Byte Order
   USHORT      m_LastPort;          // Host Byte Order
   int         m_NumberOfPorts;

   BOOLEAN     m_bOptContinuous;
   int         m_nOptWriteDelay;    // milliseconds of delay before each write

   BOOLEAN     m_bOptMultiReceiver;

   BOOLEAN     m_bTouchRecvData;

   BOOLEAN     m_bSinkMode;         // FALSE = normal I/O, TRUE = sink/source mode

   int         m_nNumBuffersToSend; // number of buffers to send in sinkmode

   int         m_nBufferSize;       // length of buffer
   int         m_nBufOffset;        // align buffer to this
   int         m_nBufAlign;         // modulo this

   BOOLEAN     m_bUseSockOptBufSize;
   int         m_nSockOptBufSize;   // socket buffer size to use

   int         m_nSockOptNoDelay;   // set TCP_NODELAY socket option

   BOOLEAN     m_bSockOptDebug;     // TRUE = set SO_DEBUG socket option

   int         m_TokenRate;          // Desired Data Transmit Rate, Bytes/Sec

   SOCKET      m_ExtraSocket;

   HANDLE      m_ThreadHandle;
   DWORD       m_ThreadID;
}
    CMD_BLOCK, *PCMD_BLOCK;

CMD_BLOCK cmdBlock[ MAX_PORTS ];
HANDLE ThreadHandles[ MAX_PORTS ];
int NumberOfThreads = 0;

typedef
struct _TEST_BLOCK
{
    //
    // Command/Configuration Data
    //
    CMD_BLOCK       m_Cmd;

    //
    // Instance Data
    //
    PCHAR           m_pBufBase;         // ptr to dynamic buffer (base)
    PCHAR           m_pBuf;             // ptr to dynamic buffer (aligned)

    SOCKET          m_Socket_fd;        // fd of network send/receive socket

    //
    // Statistics
    //
    ULARGE_INTEGER  m_tStart;
    ULARGE_INTEGER  m_tFinish;

    unsigned long   m_numCalls;         // # of I/O system calls
    double          m_nbytes;           // bytes on net
    int             m_ByteCredits;      // For Data Rate Pacing
}
    TEST_BLOCK, *PTEST_BLOCK;

int b_flag = 0;      // use mread()
int one = 1;         // for 4.3 BSD style setsockopt()
int zero = 0;        // for 4.3 BSD style setsockopt()

int verbose = 0;     // 0=print basic info, 1=print cpu rate, proc
                         // resource usage.

char fmt = 'K';      // output format:
                     // k = kilobits, K = kilobytes,
                     // m = megabits, M = megabytes, 
                     // g = gigabits, G = gigabytes

#define  UDP_GUARD_BUFFER_LENGTH  4

// MACROS To Access Common SOCKADDR_STORAGE Fields
#define SADDR_STORE_FAMILY(_SAS)               ( (_SAS)->ss_family )
#define SADDR_STORE_PORT(_SAS)                 ( ((PSOCKADDR_IN6 )(_SAS))->sin6_port)

#define SADDR_STORE_ADDR_LEN(_SAS)                                                        \
      ( (_SAS)->ss_family == AF_INET ? sizeof( SOCKADDR_IN ) : sizeof( SOCKADDR_IN6 ) )

//cmdBlock[0].m_nRemoteAddrLen = ( cmdBlock[0].m_RemoteAddr.ss_family == AF_INET ? sizeof( SOCKADDR_IN ) : sizeof( SOCKADDR_IN6 ) );

//
// Forward Procedure Prototypes
//
void TTCP_ExitTest( PTEST_BLOCK pTBlk, BOOLEAN bExitProcess );
void TTCP_ExitTestOnWSAError( PTEST_BLOCK pTBlk, PCHAR pFnc );
void TTCP_ExitTestOnCRTError( PTEST_BLOCK pTBlk, PCHAR pFnc );

int TTCP_Nread( PTEST_BLOCK pTBlk, int count );
int TTCP_Nwrite( PTEST_BLOCK pTBlk, int count );
int TTCP_mread( PTEST_BLOCK pTBlk, unsigned n );

char *outfmt(double b);

void delay( int us );

/////////////////////////////////////////////////////////////////////////////
//// Miscellaneous Support Routines
//

void TTCP_LogMsg( const char *format, ... )
{
//   FILE *logfd = NULL;
   FILE *logfd = stderr;

   char  szBuffer[ 256 ];
   va_list  marker;

   if( !logfd )
   {
//      LogOpen();
   }

   if( !logfd )
   {
      return;
   }

   va_start( marker, format );

   vsprintf_s( szBuffer, sizeof( szBuffer ), format, marker );

   fprintf( logfd, szBuffer );
}

//
// Fetch Timestamp
// ---------------
// The format is that of Windows FILETIME, but returned as
// a properly aligned ULARGE_INTEGER structure.
//
// LowPart resolution is in 100 nanosecond units.
void JitGetTimestamp( PULARGE_INTEGER timeStamp )
{
   FILETIME fileTime;

   // Sanity Check
   if( !timeStamp )
      return;

   GetSystemTimeAsFileTime( &fileTime );

   timeStamp->LowPart = fileTime.dwLowDateTime;
   timeStamp->HighPart = fileTime.dwHighDateTime;
}

void TTCP_InitStatistics( PTEST_BLOCK pTBlk )
{
    pTBlk->m_numCalls = 0;       // # of I/O system calls
    pTBlk->m_nbytes = 0;      // bytes on net

    JitGetTimestamp( &pTBlk->m_tStart );
}


void TTCP_LogStatistics( PTEST_BLOCK pTBlk )
{
   double realt;                 // user, real time (seconds)

   JitGetTimestamp( &pTBlk->m_tFinish );

   realt = ((double )pTBlk->m_tFinish.QuadPart - (double )pTBlk->m_tStart.QuadPart)/(10*1000*1000);

   if( pTBlk->m_Cmd.m_Protocol == IPPROTO_UDP )
   {
      if( pTBlk->m_Cmd.m_bTransmit )
      {
         TTCP_LogMsg( "  Statistics  : UDP%s %s -> %s\n",
            ( pTBlk->m_Cmd.m_RemoteAddr.ss_family == AF_INET ? "v4" : "v6" ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
            );
      }
      else
      {
         TTCP_LogMsg( "  Statistics  : UDP%s %s <- %s\n",
            ( pTBlk->m_Cmd.m_RemoteAddr.ss_family == AF_INET ? "v4" : "v6" ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
            );
      }
   }
   else
   {
      if( pTBlk->m_Cmd.m_bTransmit )
      {
         TTCP_LogMsg( "  Statistics  : TCP%s %s -> %s\n",
            ( pTBlk->m_Cmd.m_RemoteAddr.ss_family == AF_INET ? "v4" : "v6" ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
            );
      }
      else
      {
         TTCP_LogMsg( "  Statistics  : TCP%s %s <- %s\n",
            ( pTBlk->m_Cmd.m_RemoteAddr.ss_family == AF_INET ? "v4" : "v6" ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
            );
      }
   }

   TTCP_LogMsg( 
      "%.0f bytes in %.3f real seconds = %s/sec +++\n",
      pTBlk->m_nbytes,
      realt,
      outfmt(pTBlk->m_nbytes/realt)
      );

   TTCP_LogMsg( "numCalls: %d; msec/call: %.3f; calls/sec: %.3f\n",
      pTBlk->m_numCalls,
      1024.0 * realt/((double )pTBlk->m_numCalls),
      ((double )pTBlk->m_numCalls)/realt
      );
}

/////////////////////////////////////////////////////////////////////////////
//// TEST_BLOCK Support Routines
//
// Remarks
// A TEST_BLOCK structure is allocated for each TTCP primary test routine:
//
//    TTCP_TransmitTCP
//    TTCP_TransmitUDP
//    TTCP_ReceiveTCP
//    TTCP_ListenTCP
//    TTCP_ReceiveUDP
//
// The TEST_BLOCK is a private "instance data" structure that contains
// all of the information necessary to perform the function. It includes
// a copy of the CMD_BLOCK parameters that control the test's operation
// as well as other members to keep track of SOCKETs, buffers and
// statistics associated with the test function.
//
// Each TTCP primary test function allocates its own TEST_BLOCK and is
// responsible for eventually freeing it.
//
// In the blocking single-threaded PCATTCP version use of this mechanism
// is certainly overkill, and serves no truly useful purpose. Global
// data would have been just as effective.
//
// HOWEVER, this approach was taken in anticipation of eventually enhancing
// the test to be multi-threaded - which will actually be trivial.
//

PTEST_BLOCK
TTCP_AllocateTestBlock( PCMD_BLOCK pCmdBlk )
{
    PTEST_BLOCK pTBlk = NULL;

    pTBlk = (PTEST_BLOCK )malloc( sizeof( TEST_BLOCK ) );

    if( !pTBlk )
    {
        TTCP_ExitTestOnCRTError( NULL, "malloc" );

        return( NULL );
    }

    memset( pTBlk, 0x00, sizeof( TEST_BLOCK ) );

    memcpy( &pTBlk->m_Cmd, pCmdBlk, sizeof( CMD_BLOCK ) );

    pTBlk->m_Socket_fd = INVALID_SOCKET; // fd of network send/receive socket

    return( pTBlk );
}


void TTCP_FreeTestBlock( PTEST_BLOCK pTBlk )
{
    if( !pTBlk )
    {
        return;
    }

    if( pTBlk->m_pBufBase )
    {
        free( pTBlk->m_pBufBase );
    }

    pTBlk->m_pBufBase = NULL;

    if( pTBlk->m_Socket_fd != INVALID_SOCKET )
    {
        closesocket( pTBlk->m_Socket_fd );

        pTBlk->m_Socket_fd = INVALID_SOCKET;
    }

    pTBlk->m_Socket_fd = INVALID_SOCKET;

    free( pTBlk );
}


/////////////////////////////////////////////////////////////////////////////
//// TTCP Exit Routines
//
// Remarks
// The TTCP tests exit by calling one of 
//
//  TTCP_ExitTest           - Call for normal exit.
//  TTCP_ExitTestOnWSAError - Call to exit when Winsock error is encountered.
//  TTCP_ExitTestOnCRTError - Call to exit when OS/DOS error is encountered.
//
// The exit routines systematically free the TEST_BLOCK resources and the
// TEST_BLOCK structure itself.
//
// They exit by calling ExitThread or ExitProcess. Understand that in
// the single-threaded version a call to ExitThread simply exits the
// main program thread. That is: it is equivalent to calling ExitProcess.
//

void TTCP_ExitTest( PTEST_BLOCK pTBlk, BOOLEAN bExitProcess )
{
   if( pTBlk )
   {
      TTCP_FreeTestBlock( pTBlk );
   }

   //
   // Flush Logged Messages
   // ---------------------
   // This allows redirected console output to be saved OK.
   //
   fflush( stderr );
   fflush( stdout );

   if( bExitProcess )
   {
      WSACleanup();

      ExitProcess( 1 );
   }

   ExitThread( 1 );
}


void TTCP_ExitTestOnWSAError( PTEST_BLOCK pTBlk, PCHAR pFnc )
{
    int nError = WSAGetLastError();

    TTCP_LogMsg( "*** Winsock Error: %s Failed; Error: %d (0x%8.8X)\n",
        pFnc, nError, nError );

    TTCP_ExitTest( pTBlk, TRUE );
}


void TTCP_ExitTestOnCRTError( PTEST_BLOCK pTBlk, PCHAR pFnc )
{
    TTCP_LogMsg( "*** CRT Error: %s Failed; Error: %d (0x%8.8X)\n",
        pFnc, GetLastError(), GetLastError() );

    TTCP_ExitTest( pTBlk, TRUE );
}

#if 0
/////////////////////////////////////////////////////////////////////////////
//// CtrlHandler
//
// Purpose
// Console Ctrl-C handler.
//
// Parameters
//
// Return Value
//
// Remarks
//

BOOL WINAPI CtrlHandler( DWORD dwCtrlType )
{
   //
   // Sanity Checks
   //

   switch( dwCtrlType )
   {
   case CTRL_C_EVENT:
      TTCP_LogMsg( "Ctrl+C Event\n" );
      TTCP_LogMsg( "TTCP shutting down...\n" );
      g_bShutdown = TRUE;

      //
      // Flush Logged Messages
      // ---------------------
      // This allows redirected console output to be saved OK.
      //
      fflush( stderr );
      fflush( stdout );

      ExitProcess(1);
      return( TRUE );

   case CTRL_BREAK_EVENT:
   case CTRL_CLOSE_EVENT:
   case CTRL_SHUTDOWN_EVENT:
   default:
      return( FALSE );
   }

   return( FALSE );
}
#endif

void TTCP_SetConfigDefaults( PCMD_BLOCK pCmdBlk )
{
   memset( pCmdBlk, 0x00, sizeof( CMD_BLOCK ) );

   pCmdBlk->m_bTransmit = FALSE;

   pCmdBlk->m_Protocol = IPPROTO_TCP;
   pCmdBlk->m_NumberOfPorts = 1;

   pCmdBlk->m_Port = IPPORT_TTCP;

   // Setup Remote IP Address
   memset( &pCmdBlk->m_RemoteAddr, 0x00, sizeof( SOCKADDR_STORAGE ) );
   SADDR_STORE_FAMILY( &pCmdBlk->m_RemoteAddr ) = g_SockAddrFamily;
   pCmdBlk->m_nRemoteAddrLen = SADDR_STORE_ADDR_LEN( &pCmdBlk->m_RemoteAddr );

   // Setup Default Local IP Address
   memset( &pCmdBlk->m_LocalAddr, 0x00, sizeof( SOCKADDR_STORAGE ) );
   SADDR_STORE_FAMILY( &pCmdBlk->m_LocalAddr ) = g_SockAddrFamily;
   pCmdBlk->m_nLocalAddrLen = SADDR_STORE_ADDR_LEN( &pCmdBlk->m_LocalAddr );

   pCmdBlk->m_bOptContinuous = FALSE;
   pCmdBlk->m_nOptWriteDelay = 0;

   pCmdBlk->m_bOptMultiReceiver = FALSE;
   //   pCmdBlk->m_bOptMultiReceiver = TRUE;

   pCmdBlk->m_bTouchRecvData = FALSE;

   pCmdBlk->m_bSinkMode = TRUE;     // FALSE = normal I/O, TRUE = sink/source mode

   //
   // Setup SinkMode Default
   // ----------------------
   // SinkMode description:
   //   TRUE  -> A pattern generator is used fill the send buffer. This
   //            is done only once. Received data is simply counted.
   //   FALSE -> Data to be sent is read from stdin. Received data is
   //            written to stdout.
   //
   // g_nNumBuffersToSend specifies the number of buffers to be sent
   // in SinkMode.
   //
   pCmdBlk->m_bSinkMode = TRUE;     // FALSE = normal I/O, TRUE = sink/source mode
   pCmdBlk->m_nNumBuffersToSend = 2 * 1024; // number of buffers to send in sinkmode

   //
   // Setup Size Of Send/Receive Buffer
   //
   pCmdBlk->m_nBufferSize = 8 * 1024;

   pCmdBlk->m_nBufOffset = 0;             // align buffer to this
   pCmdBlk->m_nBufAlign = 16*1024;        // modulo this

   pCmdBlk->m_bUseSockOptBufSize = FALSE; // socket buffer size to use
   pCmdBlk->m_nSockOptBufSize = 0;        // socket buffer size to use

   pCmdBlk->m_nSockOptNoDelay = 0;        // set TCP_NODELAY socket option

   pCmdBlk->m_bSockOptDebug = FALSE;      // TRUE = set SO_DEBUG socket option
}

// Fill Buffer With Printable Characters...
void TTCP_FillPattern( PTEST_BLOCK pTBlk )
{
   register char c;
   UCHAR PBPreamble[] = "PCAUSA PCATTCP Pattern";   // 22 Bytes
   int cnt = pTBlk->m_Cmd.m_nBufferSize;
   char  *cp = pTBlk->m_pBuf;

   c = 0;

   //
   // Insert "PCAUSA Pattern" Preamble
   //
   if( cnt > 22 )
   {
      memcpy( cp, PBPreamble, 22 );
      cnt -= 22;
      cp += 22;
   }

   while( cnt-- > 0 )
   {
      while( !isprint((c&0x7F)) )
      {
         c++;
      }

      *cp++ = (c++&0x7F);
   }
}

BOOLEAN TTCP_AllocateBuffer( PTEST_BLOCK pTBlk )
{
   //
   // Setup Buffer Configuration
   //
   if( (pTBlk->m_pBufBase = (PCHAR )malloc(
      pTBlk->m_Cmd.m_nBufferSize + pTBlk->m_Cmd.m_nBufAlign)) == (PCHAR )NULL
      )
   {
      return( FALSE );  // Failed
   }

   //
   // Align The Buffer
   //
   pTBlk->m_pBuf = pTBlk->m_pBufBase;

   if (pTBlk->m_Cmd.m_nBufAlign != 0)
      pTBlk->m_pBuf += (
      pTBlk->m_Cmd.m_nBufAlign
      - ((int)pTBlk->m_pBuf % pTBlk->m_Cmd.m_nBufAlign
      ) + pTBlk->m_Cmd.m_nBufOffset) % pTBlk->m_Cmd.m_nBufAlign;

   TTCP_LogMsg( "  Buffer Size : %d; Alignment: %d/%d\n",
      pTBlk->m_Cmd.m_nBufferSize,
      pTBlk->m_Cmd.m_nBufAlign,
      pTBlk->m_Cmd.m_nBufOffset
      );

   return( TRUE );  // Success
}


/////////////////////////////////////////////////////////////////////////////
//// Primary TTCP Functions
//

/////////////////////////////////////////////////////////////////////////////
//// TTCP_TransmitTCP
//
// Purpose
// TTCP TCP transmitter.
//
// Parameters
//   pCmdBlk -         Pointer to CMD_BLOCK that contains options and other
//                     configuration information to be used for the transmit
//                     test.
//   pRemoteHostName - Pointer to null-terminated name of remote host.
//                     May be IP address or DNS-resolvable name.
//
// Return Value
// Returns zero(0) for normal return. Non-zero otherwise.
//
// Remarks
//

DWORD WINAPI TTCP_TransmitTCP( PCMD_BLOCK pCmdBlk )
{
   PTEST_BLOCK pTBlk = NULL;
   int   optlen;

   // Say Hello
   TTCP_LogMsg( "TCP Transmit Test\n" );

   //
   // Allocate Test Instance Data
   // ---------------------------
   // Allocate a TEST_BLOCK for this specific instance. The TEST_BLOCK
   // contains a copy of the calleer's CMD_BLOCK as well as additional
   // members that are used to perform this test.
   //
   pTBlk = TTCP_AllocateTestBlock( pCmdBlk );

   if( !pTBlk )
   {
      TTCP_ExitTestOnCRTError( NULL, "malloc" );

      return( 1 );
   }

   // Update Remote Port
   SADDR_STORE_PORT( &pTBlk->m_Cmd.m_RemoteAddr ) = htons( pTBlk->m_Cmd.m_Port );

   TTCP_LogMsg( "  Transmit    : TCP%s %s -> %s\n",
      ( pTBlk->m_Cmd.m_LocalAddr.ss_family == AF_INET ? "v4" : "v6" ),
      SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr ),
      SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
      );

   // Allocate The Buffer Send/Receive Data
   if( !TTCP_AllocateBuffer( pTBlk ) )
   {
      TTCP_ExitTestOnCRTError( pTBlk, "malloc" );
   }

   // Open Socket For Test
   if( ( pTBlk->m_Socket_fd = socket( g_SockAddrFamily, SOCK_STREAM, 0 ) )
      == INVALID_SOCKET
      )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "socket" );
   }

   // Bind Socket With Local Address
   if( bind(
         pTBlk->m_Socket_fd,
         (PSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr,
         pTBlk->m_Cmd.m_nLocalAddrLen
         ) == SOCKET_ERROR
      )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "bind" );
   }

   if( pTBlk->m_Cmd.m_bUseSockOptBufSize )
   {
      if( setsockopt(
            pTBlk->m_Socket_fd,
            SOL_SOCKET,
            SO_SNDBUF,
            (char * )&pTBlk->m_Cmd.m_nSockOptBufSize,
            sizeof pTBlk->m_Cmd.m_nSockOptBufSize
            ) == SOCKET_ERROR
         )
      {
         TTCP_ExitTestOnWSAError( pTBlk, "setsockopt: SO_SNDBUF" );
      }

      TTCP_LogMsg( "  SO_SNDBUF   : %d\n", pTBlk->m_Cmd.m_nSockOptBufSize );
   }

   // Start TCP Connections
   optlen = sizeof( pTBlk->m_Cmd.m_nSockOptNoDelay );

   // We are the client if transmitting
   if( pTBlk->m_Cmd.m_bSockOptDebug )
   {
      if( setsockopt(
            pTBlk->m_Socket_fd,
            SOL_SOCKET,
            SO_DEBUG,
            (PCHAR )&one,     // Boolean
            sizeof(one)
            ) == SOCKET_ERROR
         )
      {
         TTCP_ExitTestOnWSAError( pTBlk, "setsockopt: SO_DEBUG" );
      }
   }

   // Set TCP_NODELAY Send Option
   if( pTBlk->m_Cmd.m_nSockOptNoDelay )
   {
      if( setsockopt(
            pTBlk->m_Socket_fd,
            IPPROTO_TCP,
            TCP_NODELAY, 
            (PCHAR )&one,     // Boolean
            sizeof(one)
            ) == SOCKET_ERROR
         )
      {
         int nError = WSAGetLastError();
         TTCP_LogMsg( "  Error: 0x%8.8X\n", nError );
         TTCP_LogMsg("setsockopt: TCP_NODELAY option failed");
      }
   }

   // Query And Display TCP_NODELAY Send Option
   if( getsockopt(
         pTBlk->m_Socket_fd,
         IPPROTO_TCP,
         TCP_NODELAY, 
         (PCHAR )&pTBlk->m_Cmd.m_nSockOptNoDelay,
         &optlen
         ) != SOCKET_ERROR
      )
   {
      if( pTBlk->m_Cmd.m_nSockOptNoDelay )
      {
         TTCP_LogMsg( "  TCP_NODELAY : ENABLED (%d)\n",
            pTBlk->m_Cmd.m_nSockOptNoDelay );
      }
      else
      {
         TTCP_LogMsg( "  TCP_NODELAY : DISABLED (%d)\n",
            pTBlk->m_Cmd.m_nSockOptNoDelay );
      }
   }
   else
   {
      int nError = WSAGetLastError();
      TTCP_LogMsg( "  Error: 0x%8.8X\n", nError );
      TTCP_LogMsg("getsockopt: TCP_NODELAY option failed\n");
   }

   // Connect To Remote Server
   if(connect(
         pTBlk->m_Socket_fd,
         (PSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr,
         pTBlk->m_Cmd.m_nRemoteAddrLen
         )
      )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "connect" );
   }

   TTCP_LogMsg( "  Connect     : Connected to %s\n",
      SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
      );

   TTCP_InitStatistics( pTBlk );

   SetLastError(ERROR_SUCCESS);

   if( pTBlk->m_Cmd.m_nOptWriteDelay )
   {
      TTCP_LogMsg( "  Write Delay : %d milliseconds\n",
         pTBlk->m_Cmd.m_nOptWriteDelay
         );
   }

   if( pTBlk->m_Cmd.m_bSinkMode )
   {      
      TTCP_FillPattern( pTBlk );

      if( pTBlk->m_Cmd.m_bOptContinuous )
      {
         TTCP_LogMsg( "  Send Mode   : Sending Pattern CONTINUOUS\n" );

         if( pTBlk->m_Cmd.m_TokenRate > 0 )
         {
            int LastTickCount = GetTickCount();
            int ThisTickCount;

            TTCP_LogMsg( "  Data Rate   : %d bytes/second (%.2f KBps)\n", pTBlk->m_Cmd.m_TokenRate, pTBlk->m_Cmd.m_TokenRate/1024.0 );

            Sleep( 1 );
            ThisTickCount = GetTickCount();
            pTBlk->m_ByteCredits += ( pTBlk->m_Cmd.m_TokenRate/1000 ) * (ThisTickCount - LastTickCount );

            LastTickCount = ThisTickCount;

            while( pTBlk->m_ByteCredits >= pTBlk->m_Cmd.m_nBufferSize )
            {
               if( TTCP_Nwrite( pTBlk, pTBlk->m_Cmd.m_nBufferSize) == pTBlk->m_Cmd.m_nBufferSize )
               {
                  pTBlk->m_nbytes += pTBlk->m_Cmd.m_nBufferSize;
                  pTBlk->m_ByteCredits -= pTBlk->m_Cmd.m_nBufferSize;
               }
               else
               {
                  break;
               }
            }
         }
         else
         {
            while (TTCP_Nwrite( pTBlk, pTBlk->m_Cmd.m_nBufferSize) == pTBlk->m_Cmd.m_nBufferSize)
               pTBlk->m_nbytes += pTBlk->m_Cmd.m_nBufferSize;
         }
      }
      else
      {
         TTCP_LogMsg( "  Send Mode   : Send Pattern; Number of Buffers: %d\n",
            pTBlk->m_Cmd.m_nNumBuffersToSend
            );

         if( pTBlk->m_Cmd.m_TokenRate > 0 )
         {
            int LastTickCount = GetTickCount();

            TTCP_LogMsg( "  Data Rate   : %d bytes/second (%.2f KBps)\n", pTBlk->m_Cmd.m_TokenRate, pTBlk->m_Cmd.m_TokenRate/1024.0 );

            // Pace Transmit Data Rate
            while( pTBlk->m_Cmd.m_nNumBuffersToSend )
            {
               int ThisTickCount;

               Sleep( 1 );
               ThisTickCount = GetTickCount();
               pTBlk->m_ByteCredits += ( pTBlk->m_Cmd.m_TokenRate/1000 ) * (ThisTickCount - LastTickCount );

               LastTickCount = ThisTickCount;

               while( pTBlk->m_Cmd.m_nNumBuffersToSend > 0
                  && pTBlk->m_ByteCredits >= pTBlk->m_Cmd.m_nBufferSize
                  )
               {
                  if( TTCP_Nwrite( pTBlk, pTBlk->m_Cmd.m_nBufferSize) == pTBlk->m_Cmd.m_nBufferSize )
                  {
                     --pTBlk->m_Cmd.m_nNumBuffersToSend;
                     pTBlk->m_nbytes += pTBlk->m_Cmd.m_nBufferSize;
                     pTBlk->m_ByteCredits -= pTBlk->m_Cmd.m_nBufferSize;
                  }
                  else
                  {
                     break;
                  }
               }
            }
         }
         else
         {
            while (pTBlk->m_Cmd.m_nNumBuffersToSend--
               && TTCP_Nwrite( pTBlk, pTBlk->m_Cmd.m_nBufferSize) == pTBlk->m_Cmd.m_nBufferSize)
               pTBlk->m_nbytes += pTBlk->m_Cmd.m_nBufferSize;
         }
      }
   }
   else
   {
      register int cnt;

      TTCP_LogMsg( "  Send Mode   : Read from STDIN\n" );

      // Read From stdin And Write To Remote
      while (fgets(pTBlk->m_pBuf, pTBlk->m_Cmd.m_nBufferSize, stdin))
      {
          int cnt2;

          cnt = strlen(pTBlk->m_pBuf);
          cnt2 = TTCP_Nwrite(pTBlk, cnt);
          pTBlk->m_nbytes += cnt;

          if (cnt2 != cnt)
              break;
      }
   }

   if(GetLastError())
      TTCP_ExitTestOnCRTError( pTBlk, "IO" );

   TTCP_LogStatistics( pTBlk );

   TTCP_FreeTestBlock( pTBlk );

   return( 0 );
}


/////////////////////////////////////////////////////////////////////////////
//// TTCP_TransmitUDP
//
// Purpose
// TTCP UDP transmitter.
//
// Parameters
//   pCmdBlk -         Pointer to CMD_BLOCK that contains options and other
//                     configuration information to be used for the transmit
//                     test.
//   pRemoteHostName - Pointer to null-terminated name of remote host.
//                     May be IP address or DNS-resolvable name.
//
// Return Value
// Returns zero(0) for normal return. Non-zero otherwise.
//
// Remarks
//

DWORD WINAPI TTCP_TransmitUDP( PCMD_BLOCK pCmdBlk )
{
   PTEST_BLOCK pTBlk = NULL;

   // Say Hello
   TTCP_LogMsg( "UDP Transmit Test\n" );

   //
   // Allocate Test Instance Data
   // ---------------------------
   // Allocate a TEST_BLOCK for this specific instance. The TEST_BLOCK
   // contains a copy of the calleer's CMD_BLOCK as well as additional
   // members that are used to perform this test.
   //
   pTBlk = TTCP_AllocateTestBlock( pCmdBlk );

   if( !pTBlk )
   {
      TTCP_ExitTestOnCRTError( NULL, "malloc" );

      return( 1 );
   }

   // Update Remote Port
   SADDR_STORE_PORT( &pTBlk->m_Cmd.m_RemoteAddr ) = htons( pTBlk->m_Cmd.m_Port );

   TTCP_LogMsg( "  Transmit    : UDP%s %s -> %s\n",
      ( pTBlk->m_Cmd.m_LocalAddr.ss_family == AF_INET ? "v4" : "v6" ),
      SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr ),
      SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
      );

   // Setup Buffer Configuration
   if( pTBlk->m_Cmd.m_nBufferSize <= UDP_GUARD_BUFFER_LENGTH )
   {
      pTBlk->m_Cmd.m_nBufferSize = UDP_GUARD_BUFFER_LENGTH + 1; // send more than the sentinel size
   }

   // Allocate The Buffer Send/Receive Data
   if( !TTCP_AllocateBuffer( pTBlk ) )
   {
      TTCP_ExitTestOnCRTError( pTBlk, "malloc" );
   }

   // Open Socket For Test
   if( (pTBlk->m_Socket_fd = socket( g_SockAddrFamily, SOCK_DGRAM, 0 ) ) == INVALID_SOCKET
      )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "socket" );
   }

   // Bind Socket With Local Address
   if( bind(
      pTBlk->m_Socket_fd,
         (PSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr,
         pTBlk->m_Cmd.m_nLocalAddrLen
         ) == SOCKET_ERROR
      )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "bind" );
   }

   if( pTBlk->m_Cmd.m_bUseSockOptBufSize )
   {
      if( setsockopt(
            pTBlk->m_Socket_fd,
            SOL_SOCKET,
            SO_SNDBUF,
            (char * )&pTBlk->m_Cmd.m_nSockOptBufSize,
            sizeof pTBlk->m_Cmd.m_nSockOptBufSize
            ) == SOCKET_ERROR
         )
      {
         TTCP_ExitTestOnWSAError( pTBlk, "setsockopt: SO_SNDBUF" );
      }

      TTCP_LogMsg( "  SO_SNDBUF   : %d\n", pTBlk->m_Cmd.m_nSockOptBufSize );
   }

   TTCP_InitStatistics( pTBlk );
   SetLastError(ERROR_SUCCESS);

   if( pTBlk->m_Cmd.m_nOptWriteDelay )
   {
      TTCP_LogMsg( "  Write Delay : %d milliseconds\n",
         pTBlk->m_Cmd.m_nOptWriteDelay
         );
   }

   if( pTBlk->m_Cmd.m_bSinkMode )
   {      
      TTCP_FillPattern( pTBlk );

      TTCP_Nwrite( pTBlk, UDP_GUARD_BUFFER_LENGTH ); /* rcvr start */

      if( pTBlk->m_Cmd.m_bOptContinuous )
      {
         int LastTickCount = GetTickCount();

         TTCP_LogMsg( "  Send Mode   : Send Pattern CONTINUOUS\n" );

         if( pTBlk->m_Cmd.m_TokenRate > 0 )
         {
            TTCP_LogMsg( "  Data Rate   : %d bytes/second (%.2f KBps)\n", pTBlk->m_Cmd.m_TokenRate, pTBlk->m_Cmd.m_TokenRate/1024.0 );

            while( 1 )
            {
               int ThisTickCount;

               Sleep( 1 );
               ThisTickCount = GetTickCount();
               pTBlk->m_ByteCredits += ( pTBlk->m_Cmd.m_TokenRate/1000 ) * (ThisTickCount - LastTickCount );

               LastTickCount = ThisTickCount;

               while( pTBlk->m_ByteCredits >= pTBlk->m_Cmd.m_nBufferSize )
               {
                  if( TTCP_Nwrite( pTBlk, pTBlk->m_Cmd.m_nBufferSize) == pTBlk->m_Cmd.m_nBufferSize )
                  {
                     pTBlk->m_nbytes += pTBlk->m_Cmd.m_nBufferSize;
                     pTBlk->m_ByteCredits -= pTBlk->m_Cmd.m_nBufferSize;
                  }
                  else
                  {
                     break;
                  }
               }
            }
         }
         else
         {
            while (TTCP_Nwrite( pTBlk, pTBlk->m_Cmd.m_nBufferSize) == pTBlk->m_Cmd.m_nBufferSize)
               pTBlk->m_nbytes += pTBlk->m_Cmd.m_nBufferSize;
         }
      }
      else
      {
         TTCP_LogMsg( "  Send Mode   : Send Pattern; Number of Buffers: %d\n",
            pTBlk->m_Cmd.m_nNumBuffersToSend
            );

         if( pTBlk->m_Cmd.m_TokenRate > 0 )
         {
            int LastTickCount = GetTickCount();

            TTCP_LogMsg( "  Data Rate   : %d bytes/second (%.2f KBps)\n", pTBlk->m_Cmd.m_TokenRate, pTBlk->m_Cmd.m_TokenRate/1024.0 );

            // Pace Transmit Data Rate
            while( pTBlk->m_Cmd.m_nNumBuffersToSend )
            {
               int ThisTickCount;

               Sleep( 1 );
               ThisTickCount = GetTickCount();
               pTBlk->m_ByteCredits += ( pTBlk->m_Cmd.m_TokenRate/1000 ) * (ThisTickCount - LastTickCount );

               LastTickCount = ThisTickCount;

               while( pTBlk->m_Cmd.m_nNumBuffersToSend > 0
                  && pTBlk->m_ByteCredits >= pTBlk->m_Cmd.m_nBufferSize
                  )
               {
                  if( TTCP_Nwrite( pTBlk, pTBlk->m_Cmd.m_nBufferSize) == pTBlk->m_Cmd.m_nBufferSize )
                  {
                     --pTBlk->m_Cmd.m_nNumBuffersToSend;
                     pTBlk->m_nbytes += pTBlk->m_Cmd.m_nBufferSize;
                     pTBlk->m_ByteCredits -= pTBlk->m_Cmd.m_nBufferSize;
                  }
                  else
                  {
                     break;
                  }
               }
            }
         }
         else
         {
            while (pTBlk->m_Cmd.m_nNumBuffersToSend--
               && TTCP_Nwrite( pTBlk, pTBlk->m_Cmd.m_nBufferSize) == pTBlk->m_Cmd.m_nBufferSize)
               pTBlk->m_nbytes += pTBlk->m_Cmd.m_nBufferSize;
         }
      }

      TTCP_Nwrite( pTBlk, UDP_GUARD_BUFFER_LENGTH ); /* rcvr end */
   }
   else
   {
      register int cnt;

      TTCP_LogMsg( "  Send Mode   : Read from STDIN\n" );

      // Read From stdin And Write To Remote
      while (fgets(pTBlk->m_pBuf, pTBlk->m_Cmd.m_nBufferSize, stdin))
      {
          int cnt2;

          cnt = strlen(pTBlk->m_pBuf);
          cnt2 = TTCP_Nwrite(pTBlk, cnt);
          pTBlk->m_nbytes += cnt;

          if (cnt2 != cnt)
              break;
      }
   }

   if(GetLastError())
      TTCP_ExitTestOnCRTError( pTBlk, "IO" );

   TTCP_LogStatistics( pTBlk );

   // Send First Test End Guard Buffer
   TTCP_Nwrite( pTBlk, UDP_GUARD_BUFFER_LENGTH );   // rcvr end

   //
   // Pause To Allow Receiver To Flush Receive Buffers
   // ------------------------------------------------
   // PCATTCP Version 2.01.01.06 and prior (as well as the original TTCP
   // application)were subject to failure at various points. This was due
   // to the fact that UDP has no flow control, so a UDP transfer overwhelms
   // the receiving computer. The reason it was failing was that the end marker
   // of the data transfer was getting lost in the network while the receiving
   // computer was being overwhelmed with data. This would cause the receiving
   // computer to hang, waiting for more data, but the transmitting computer
   // had already finished sending all the data along with the end markers.
   //
   // The folks at Clarkson University identified a simple fix for this
   // weakness which PCAUSA has adopted in V2.01.01.07. See the URL:
   //
   // http://www.clarkson.edu/projects/itl/HOWTOS/mcnair_summer2002/Paper/User_Manual_PCATTCP_Controller_1.00.htm
   //
   // A simple pause by the transmitter before the end markers were sent would
   // allow the receiving computer time to free its internal receiving buffers
   // and obtain an end marker.
   //
   Sleep( 1000 );

   TTCP_Nwrite( pTBlk, UDP_GUARD_BUFFER_LENGTH );   // rcvr end
   TTCP_Nwrite( pTBlk, UDP_GUARD_BUFFER_LENGTH );   // rcvr end
   TTCP_Nwrite( pTBlk, UDP_GUARD_BUFFER_LENGTH );   // rcvr end

   TTCP_FreeTestBlock( pTBlk );

   return( 0 );
}


/////////////////////////////////////////////////////////////////////////////
//// TTCP_ReceiveTCP
//
// Purpose
// TTCP TCP stream receiver.
//
// Parameters
//   pCmdBlk - Pointer to CMD_BLOCK that contains options and other
//             configuration information to be used to receive on
//             the stream.
//   conn_fd - The connection socket, which returned from a call to
//             accept which is made in TTCP_ListenTCP.
//
// Return Value
// Returns zero(0) for normal return. Non-zero otherwise.
//
// Remarks
// The TTCP_ListenTCP routine listens for connections on the TTCP port.
// When a connection is accepted in TTCP_ListenTCP it then calls this
// function to perform the TCP receive test on the connection socket.
//
// Ownership of the conn_fd socket is passed to this routine, which is
// responsible for eventually closing it.
//

DWORD WINAPI TTCP_ReceiveTCP( PCMD_BLOCK pCmdBlk )
{
   PTEST_BLOCK pTBlk = NULL;

   //
   // Allocate Test Instance Data
   // ---------------------------
   // Allocate a TEST_BLOCK for this specific instance. The TEST_BLOCK
   // contains a copy of the calleer's CMD_BLOCK as well as additional
   // members that are used to perform this test.
   //
   pTBlk = TTCP_AllocateTestBlock( pCmdBlk );

   if( !pTBlk )
   {
      closesocket( pCmdBlk->m_ExtraSocket );

      TTCP_ExitTestOnCRTError( NULL, "malloc" );

      return( 1 );
   }

   // Save The Connection Socket
   pTBlk->m_Socket_fd = pCmdBlk->m_ExtraSocket;

   pTBlk->m_Cmd.m_Protocol = IPPROTO_TCP;

   // Allocate The Buffer Send/Receive Data
   if( !TTCP_AllocateBuffer( pTBlk ) )
   {
      TTCP_ExitTestOnCRTError( pTBlk, "malloc" );

      return( 1 );
   }

   TTCP_InitStatistics( pTBlk );
   SetLastError(ERROR_SUCCESS);

   if( pTBlk->m_Cmd.m_bSinkMode )
   {      
      register int cnt;

      // Discard Received Data
      TTCP_LogMsg( "  Receive Mode: Sinking (discarding) Data\n" );

      while( (cnt=TTCP_Nread( pTBlk, pTBlk->m_Cmd.m_nBufferSize) ) > 0)
      {
         pTBlk->m_nbytes += cnt;
      }
   }
   else
   {
      register int cnt;

      // Read From Remote And Write To stdout
      TTCP_LogMsg( "  Receive Mode: Writing Received Data To STDOUT\n" );

      while( ( cnt = TTCP_Nread( pTBlk, pTBlk->m_Cmd.m_nBufferSize ) ) > 0)
      {
         fwrite(pTBlk->m_pBuf, 1, cnt, stdout);
         fflush(NULL);
         pTBlk->m_nbytes += cnt;
      }
   }

   if(GetLastError())
      TTCP_ExitTestOnCRTError( pTBlk, "IO" );

   TTCP_LogStatistics( pTBlk );

   TTCP_FreeTestBlock( pTBlk );

   return( 0 );
}


/////////////////////////////////////////////////////////////////////////////
//// TTCP_ListenTCP
//
// Purpose
// TTCP TCP connection listener.
//
// Parameters
//   pCmdBlk - Pointer to CMD_BLOCK that contains options and other
//             configuration information to be used to listen for
//             connections.
//
// Return Value
// Returns zero(0) for normal return. Non-zero otherwise.
//
// Remarks
// The TTCP_ListenTCP routine listens for connections on the TTCP port.
//
// When a connection is accepted TTCP_ListenTCP calls TTCP_ReceiveTCP
// function to perform the actual TCP receive test.
//
// Ownership of the conn_fd socket is passed to TTCP_ReceiveTCP routine,
// which is responsible for eventually closing conn_fd.
//

DWORD WINAPI TTCP_ListenTCP( PCMD_BLOCK pCmdBlk )
{
   PTEST_BLOCK    pTBlk = NULL;
   SOCKET         conn_fd;
   BOOLEAN        bDoAccept = TRUE;
   CHAR           szLocalHostName[ 128 ];

   TTCP_LogMsg( "TCP Receive Test\n" );

   //
   // Allocate Test Instance Data
   // ---------------------------
   // Allocate a TEST_BLOCK for this specific instance. The TEST_BLOCK
   // contains a copy of the calleer's CMD_BLOCK as well as additional
   // members that are used to perform this test.
   //
   pTBlk = TTCP_AllocateTestBlock( pCmdBlk );

   if( !pTBlk )
   {
      TTCP_ExitTestOnCRTError( NULL, "malloc" );

      return( 1 );
   }

   sprintf_s( szLocalHostName, sizeof( szLocalHostName ), "Unknown" );
   gethostname( szLocalHostName, 128 );
   TTCP_LogMsg( "  Local Host  : %s\n", szLocalHostName );

   // Setup Local IP Addresses For Test
   SADDR_STORE_PORT( &pTBlk->m_Cmd.m_LocalAddr ) = ntohs( pTBlk->m_Cmd.m_Port );

   pTBlk->m_Cmd.m_nLocalAddrLen = SADDR_STORE_ADDR_LEN( &pTBlk->m_Cmd.m_LocalAddr );

   // Setup Remote IP Addresses For Test
   memset( &pTBlk->m_Cmd.m_RemoteAddr, 0x00, sizeof( SOCKADDR_STORAGE ) );

   SADDR_STORE_FAMILY( &pTBlk->m_Cmd.m_RemoteAddr ) = g_SockAddrFamily;
   SADDR_STORE_PORT( &pTBlk->m_Cmd.m_RemoteAddr ) = ntohs( pTBlk->m_Cmd.m_Port );

   pTBlk->m_Cmd.m_nRemoteAddrLen = SADDR_STORE_ADDR_LEN( &pTBlk->m_Cmd.m_RemoteAddr );

   // Open Listening Socket
   if( (pTBlk->m_Socket_fd = socket( g_SockAddrFamily, SOCK_STREAM, 0 ) ) == INVALID_SOCKET )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "socket" );
   }

   // Bind Socket With Local Address
   if( bind(
         pTBlk->m_Socket_fd,
         (PSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr,
         pTBlk->m_Cmd.m_nLocalAddrLen
         ) == SOCKET_ERROR
      )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "bind" );
   }

   if( pTBlk->m_Cmd.m_bUseSockOptBufSize )
   {
      if( setsockopt(
            pTBlk->m_Socket_fd,
            SOL_SOCKET,
            SO_RCVBUF,
            (char * )&pTBlk->m_Cmd.m_nSockOptBufSize,
            sizeof pTBlk->m_Cmd.m_nSockOptBufSize
            ) == SOCKET_ERROR
         )
      {
         TTCP_ExitTestOnWSAError( pTBlk, "setsockopt: SO_RCVBUF" );
      }

      TTCP_LogMsg( "  SO_RCVBUF   : %d\n", pTBlk->m_Cmd.m_nSockOptBufSize );
   }

   //
   // Start TCP Connections
   // ---------------------
   // We Are The Server.
   //
   listen( pTBlk->m_Socket_fd, 0 );  // allow a queue of 0

   if( pTBlk->m_Cmd.m_bSockOptDebug )
   {
      if( setsockopt(
            pTBlk->m_Socket_fd,
            SOL_SOCKET,
            SO_DEBUG,
            (PCHAR )&one,        // Boolean
            sizeof(one)
            ) == SOCKET_ERROR
         )
      {
         TTCP_ExitTestOnWSAError( pTBlk, "setsockopt: SO_DEBUG" );
      }
   }

   pTBlk->m_Cmd.m_nRemoteAddrLen = SADDR_STORE_ADDR_LEN( &pTBlk->m_Cmd.m_RemoteAddr );

   while( bDoAccept )
   {
      DWORD    tid;     // Thread ID

      TTCP_LogMsg( "**************\n" );
      TTCP_LogMsg( "  Listening...: On TCP%s %s\n",
         ( pTBlk->m_Cmd.m_RemoteAddr.ss_family == AF_INET ? "v4" : "v6" ),
         SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr )
         );

      if( ( conn_fd = accept( pTBlk->m_Socket_fd, (PSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr,
         &pTBlk->m_Cmd.m_nRemoteAddrLen) ) == SOCKET_ERROR)
      {
         TTCP_ExitTestOnWSAError( pTBlk, "accept" );
      }
      else
      {
         SOCKADDR_STORAGE peer;
         int peerlen = sizeof(peer);

         if (getpeername( conn_fd, (PSOCKADDR ) &peer, 
            &peerlen) == SOCKET_ERROR)
         {
            TTCP_ExitTestOnWSAError( pTBlk, "getpeername" );
         }

         memcpy( &pCmdBlk->m_RemoteAddr, &peer, sizeof( SOCKADDR_STORAGE ) );

         TTCP_LogMsg( "\n  Accept      : TCP%s %s <- %s\n",
            ( pTBlk->m_Cmd.m_RemoteAddr.ss_family == AF_INET ? "v4" : "v6" ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr ),
            SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
            );
      }

      pTBlk->m_Cmd.m_ExtraSocket = conn_fd;

      if( pCmdBlk->m_bOptMultiReceiver )
      {
         CreateThread(
            NULL,
            0,
            (LPTHREAD_START_ROUTINE )TTCP_ReceiveTCP,
            &pTBlk->m_Cmd,
            0,
            &tid
            );
      }
      else
      {
         TTCP_ReceiveTCP( &pTBlk->m_Cmd );
      }

      if( !pTBlk->m_Cmd.m_bOptContinuous )
      {
         bDoAccept = FALSE;
      }
   }

   TTCP_FreeTestBlock( pTBlk );

   return( 0 );
}

/////////////////////////////////////////////////////////////////////////////
//// TTCP_ReceiveUDP
//
// Purpose
// TTCP UDP datagram receiver.
//
// Parameters
//   pCmdBlk - Pointer to CMD_BLOCK that contains options and other
//             configuration information.
//
// Return Value
// Returns zero(0) for normal return. Non-zero otherwise.
//
// Remarks
// The TTCP_ReceiveUDP routine performs the TTCP UDP receive test.
//

DWORD WINAPI TTCP_ReceiveUDP( PCMD_BLOCK pCmdBlk )
{
   PTEST_BLOCK pTBlk = NULL;
   BOOLEAN     bContinue = TRUE;

   TTCP_LogMsg( "UDP Receive Test\n" );

   //
   // Allocate Test Instance Data
   // ---------------------------
   // Allocate a TEST_BLOCK for this specific instance. The TEST_BLOCK
   // contains a copy of the calleer's CMD_BLOCK as well as additional
   // members that are used to perform this test.
   //
   pTBlk = TTCP_AllocateTestBlock( pCmdBlk );

   if( !pTBlk )
   {
      TTCP_ExitTestOnCRTError( NULL, "malloc" );

      return( 1 );
   }

   // Update Local Port
   SADDR_STORE_PORT( &pTBlk->m_Cmd.m_LocalAddr ) = htons( pTBlk->m_Cmd.m_Port );
   pTBlk->m_Cmd.m_nLocalAddrLen = SADDR_STORE_ADDR_LEN( &pTBlk->m_Cmd.m_LocalAddr );

   TTCP_LogMsg( "  Protocol   : UDP%s\n",
      ( pTBlk->m_Cmd.m_LocalAddr.ss_family == AF_INET ? "v4" : "v6" )
      );

   TTCP_LogMsg( "  Port       : %d\n", pTBlk->m_Cmd.m_Port );

   // Setup Buffer Configuration
   if( pTBlk->m_Cmd.m_nBufferSize <= UDP_GUARD_BUFFER_LENGTH )
   {
      pTBlk->m_Cmd.m_nBufferSize = UDP_GUARD_BUFFER_LENGTH + 1; // send more than the sentinel size
   }

   // Allocate The Buffer Send/Receive Data
   if( !TTCP_AllocateBuffer( pTBlk ) )
   {
      TTCP_ExitTestOnCRTError( pTBlk, "malloc" );
   }

   // Open Socket For Test
   if( (pTBlk->m_Socket_fd = socket( g_SockAddrFamily, SOCK_DGRAM, 0 ) ) == INVALID_SOCKET )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "socket" );
   }

   // Bind Socket With Local Address
   if( bind(
         pTBlk->m_Socket_fd,
         (PSOCKADDR )&pTBlk->m_Cmd.m_LocalAddr,
         pTBlk->m_Cmd.m_nLocalAddrLen
         ) == SOCKET_ERROR
      )
   {
      TTCP_ExitTestOnWSAError( pTBlk, "bind" );
   }

   if( pTBlk->m_Cmd.m_bUseSockOptBufSize )
   {
      if( setsockopt(
            pTBlk->m_Socket_fd,
            SOL_SOCKET,
            SO_RCVBUF,
            (char * )&pTBlk->m_Cmd.m_nSockOptBufSize,
            sizeof pTBlk->m_Cmd.m_nSockOptBufSize
            ) == SOCKET_ERROR
         )
      {
         TTCP_ExitTestOnWSAError( pTBlk, "setsockopt: SO_RCVBUF" );
      }

      TTCP_LogMsg( "  SO_RCVBUF   : %d\n", pTBlk->m_Cmd.m_nSockOptBufSize );
   }

   while( bContinue )
   {
      TTCP_InitStatistics( pTBlk );
      SetLastError(ERROR_SUCCESS);

      if( pTBlk->m_Cmd.m_bSinkMode )
      {      
         register int cnt;
         int going = 0;
         BOOLEAN bIsFirst = TRUE;

         while( (cnt=TTCP_Nread( pTBlk, pTBlk->m_Cmd.m_nBufferSize)) > 0 )
         {
            if( cnt <= UDP_GUARD_BUFFER_LENGTH )
            {
               if( going )
               {
                  going = 0;
                  break;    /* "EOF" */
               }

               going = 1;
               TTCP_InitStatistics( pTBlk );
            }
            else
            {
               if( bIsFirst )
               {
                  TTCP_LogMsg( "  recvfrom    : UDP%s <- %s\n",
                     ( pTBlk->m_Cmd.m_RemoteAddr.ss_family == AF_INET ? "v4" : "v6" ),
                     SockAddrToString( (LPSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr )
                     );

                  bIsFirst = FALSE;
               }

               pTBlk->m_nbytes += cnt;
            }
         }
      }
      else
      {
         register int cnt;

         //
         // Read From Remote And Write To stdout
         //
         while( ( cnt = TTCP_Nread( pTBlk, pTBlk->m_Cmd.m_nBufferSize ) ) > 0)
         {
            fwrite(pTBlk->m_pBuf, 1, cnt, stdout);
            fflush(NULL);
            pTBlk->m_nbytes += cnt;
         }
      }

      if(GetLastError())
         TTCP_ExitTestOnCRTError( pTBlk, "IO" );

      if( pTBlk->m_nbytes > 0 )
      {
         TTCP_LogStatistics( pTBlk );
      }

      if( !pTBlk->m_Cmd.m_bOptContinuous )
      {
         bContinue = FALSE;
      }
   }

   TTCP_FreeTestBlock( pTBlk );

   return( 0 );
}

//
// Usage Message
//
char Usage[] = "\
       Usage: pcattcp -t [-4|-6] [-options] host [ < in ]\n\
       pcattcp -r [-4|-6] [-options > out]\n\
Common options:\n\
   -4     use IPv4 (default)\n\
   -6     use IPv6\n\
   -l ##  length of bufs read from or written to network (default 8192)\n\
   -u     use UDP instead of TCP\n\
   -p ##  port number to send to or listen at (default 5001)\n\
            Can specify multiple sequential ports two ways:\n\
            -p #first-#last\n\
            -p #first+#additional\n\
   -s     toggle sinkmode (enabled by default)\n\
            sinkmode enabled:\n\
               -t: source (transmit) fabricated pattern\n\
               -r: sink (discard) all received data\n\
            sinkmode disabled:\n\
               -t: reads data to be transmitted from stdin\n\
               -r: writes received data to stdout\n\
   -A     align the start of buffers to this modulus (default 16384)\n\
   -O     start buffers at this offset from the modulus (default 0)\n\
   -v     verbose: print more statistics\n\
   -d     set SO_DEBUG socket option\n\
   -b ##  set socket buffer size (if supported)\n\
   -f X   format for rate: k,K = kilo{bit,byte}; m,M = mega; g,G = giga\n\
   -c       -t: send continuously\n\
            -r: accept multiple connections sequentially\n\
   -a     bind to local host interface IP address\n\
Options specific to -t:\n\
   -n ##  number of source bufs written to network (default 2048)\n\
   -D     don't buffer TCP writes (sets TCP_NODELAY socket option)\n\
   -w ##  milliseconds of delay before each write (default 0)\n\
   -R ##  desired transmit data rate in bytes/second\n\
Options specific to -r:\n\
   -M     concurrent TCP/UDP multithreaded receiver\n\
   -B     for -s, only output full blocks as specified by -l (for TAR)\n\
   -T     \"touch\": access each byte as it's read\n\
";


/////////////////////////////////////////////////////////////////////////////
//// TTCP_ParseCommandLine
//
// Purpose
// Parse the console application command-line arguments and setup the
// CMD_BLOCK/
//
// Parameters
//   pCmdBlk - Pointer to CMD_BLOCK to be filled with parsed option
//             information.
//
// Return Value
// Returns zero(0) for normal return. Non-zero otherwise.
//
// Remarks
//

int TTCP_ParseCommandLine( PCMD_BLOCK pCmdBlk, int argc, char **argv )
{
   int     c;
   char    *s;

   if (argc < 2)
   {
      fprintf( stderr, Usage );
      return( !0 );
   }

   while (optind != argc)
   {
      c = getopt(argc, argv, "46cdrstuvBDMTb:f:l:n:p:A:O:w:R:a:" );

      switch (c)
      {
      case EOF:
         optarg = argv[optind];
         optind++;
         break;

      case '4':
         {
            g_SockAddrFamily = AF_INET;

            // Setup Remote IP Address
            memset( &pCmdBlk->m_RemoteAddr, 0x00, sizeof( SOCKADDR_STORAGE ) );
            SADDR_STORE_FAMILY( &pCmdBlk->m_RemoteAddr ) = g_SockAddrFamily;
            pCmdBlk->m_nRemoteAddrLen = SADDR_STORE_ADDR_LEN( &pCmdBlk->m_RemoteAddr );

            // Setup Local IP Address
            memset( &pCmdBlk->m_LocalAddr, 0x00, sizeof( SOCKADDR_STORAGE ) );
            SADDR_STORE_FAMILY( &pCmdBlk->m_LocalAddr ) = g_SockAddrFamily;
            pCmdBlk->m_nLocalAddrLen = SADDR_STORE_ADDR_LEN( &pCmdBlk->m_LocalAddr );
         }
         break;

      case '6':
         {
            g_SockAddrFamily = AF_INET6;

            // Setup Remote IP Address
            memset( &pCmdBlk->m_RemoteAddr, 0x00, sizeof( SOCKADDR_STORAGE ) );
            SADDR_STORE_FAMILY( &pCmdBlk->m_RemoteAddr ) = g_SockAddrFamily;
            pCmdBlk->m_nRemoteAddrLen = SADDR_STORE_ADDR_LEN( &pCmdBlk->m_RemoteAddr );

            // Setup Local IP Address
            memset( &pCmdBlk->m_LocalAddr, 0x00, sizeof( SOCKADDR_STORAGE ) );
            SADDR_STORE_FAMILY( &pCmdBlk->m_LocalAddr ) = g_SockAddrFamily;
            pCmdBlk->m_nLocalAddrLen = SADDR_STORE_ADDR_LEN( &pCmdBlk->m_LocalAddr );
         }
         break;

      case 'B':
         b_flag = 1;
         break;

      case 't':
         pCmdBlk->m_bTransmit = TRUE;
         break;

      case 'r':
         pCmdBlk->m_bTransmit = FALSE;
         break;

      case 'c':
         pCmdBlk->m_bOptContinuous = TRUE;
         break;

      case 'd':
         pCmdBlk->m_bSockOptDebug = 1;
         break;

      case 'D':
         pCmdBlk->m_nSockOptNoDelay = 1;
         break;

      case 'M':
         pCmdBlk->m_bOptContinuous = TRUE;
         pCmdBlk->m_bTransmit = FALSE;
         pCmdBlk->m_bOptMultiReceiver = TRUE;
         break;

      case 'n':
         pCmdBlk->m_nNumBuffersToSend = atoi(optarg);
         break;

      case 'l':
         pCmdBlk->m_nBufferSize = atoi(optarg);
         break;

      case 's':
         pCmdBlk->m_bSinkMode = !pCmdBlk->m_bSinkMode;
         break;

      case 'p':
         // Fetch First Port Number
         pCmdBlk->m_Port = atoi(optarg);

         // Check for Multi-Port First-Last Parameter Specification
         if( ( s = strchr( optarg, '-' ) ) != NULL )
         {
            pCmdBlk->m_LastPort = atoi(++s);
            if( pCmdBlk->m_LastPort <= pCmdBlk->m_Port )
               break;

            if( pCmdBlk->m_LastPort > pCmdBlk->m_Port + MAX_PORTS )
               pCmdBlk->m_LastPort = pCmdBlk->m_Port + MAX_PORTS;

            pCmdBlk->m_NumberOfPorts = pCmdBlk->m_LastPort - pCmdBlk->m_Port + 1;
         }

         // Check for Multi-Port First_Number Parameter Specification
         if( ( s = strchr( optarg, '+' ) ) != NULL )
         {
            pCmdBlk->m_NumberOfPorts = atoi(++s) + 1;

            if( pCmdBlk->m_NumberOfPorts < 1 )
               break;

            if( pCmdBlk->m_NumberOfPorts > MAX_PORTS )
               pCmdBlk->m_NumberOfPorts = MAX_PORTS;

            pCmdBlk->m_LastPort = pCmdBlk->m_Port + pCmdBlk->m_NumberOfPorts;
         }
         break;

      case 'u':
         pCmdBlk->m_Protocol = IPPROTO_UDP;
         break;

      case 'v':
         verbose = 1;
         break;

      case 'A':
         pCmdBlk->m_nBufAlign = atoi(optarg);
         break;

      case 'a':
         {
            PADDRINFOA  pAddrInfo;

            // Fetch Local InterfaceIP Address
            pAddrInfo = ResolveAddress(
               optarg,
               "\0",                // Service Name
               g_SockAddrFamily,
               pCmdBlk->m_Protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM,
               pCmdBlk->m_Protocol
               );

            if( !pAddrInfo )
            {
               TTCP_ExitTestOnWSAError( NULL, "Could not resolve local interface IP address" );
            }

            memcpy( &pCmdBlk->m_LocalAddr, pAddrInfo->ai_addr, pAddrInfo->ai_addrlen );

            freeaddrinfo( pAddrInfo );
         }
         break;

      case 'O':
         pCmdBlk->m_nBufOffset = atoi(optarg);
         break;

      case 'b':
         pCmdBlk->m_bUseSockOptBufSize = TRUE;
         pCmdBlk->m_nSockOptBufSize = atoi(optarg);
         if( pCmdBlk->m_nSockOptBufSize < 0 )
         {
            pCmdBlk->m_nSockOptBufSize = 0;
         }
         break;

      case 'f':
         fmt = *optarg;
         break;

      case 'T':
         pCmdBlk->m_bTouchRecvData = 1;
         break;

      case 'w':
         pCmdBlk->m_nOptWriteDelay = atoi(optarg);
         break;

      case 'R':
         pCmdBlk->m_TokenRate = atoi(optarg);
         if( pCmdBlk->m_TokenRate < 0 )
         {
            pCmdBlk->m_TokenRate = 0;
         }
         break;

      default:
         fprintf( stderr, Usage );
         return( !0 );
      }
   }

   return( 0 );
}

// In place conversion of unicode to ascii
// Yes, it is ugly...
void UnicodeToAnsi(TCHAR* s)
{
	int cc;
	int cw=wcslen(s);
	if (s==NULL) return NULL;
	cc=WideCharToMultiByte(CP_ACP,0,s,cw,NULL,0,NULL,NULL);
	if (cc==0) return NULL;
	cc=WideCharToMultiByte(CP_ACP,0,s,cw,s,cc,NULL,NULL);
	((CHAR*)(s))[cc]='\0';
}

/////////////////////////////////////////////////////////////////////////////
//// main
//
// Purpose
// Console application main program entry point.
//
// Parameters
//
// Return Value
// Returns zero(0) for normal return. Non-zero otherwise.
//
// Remarks
//
int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
   DWORD nResult;
   int i;

   //
   // Convert argv to ascii/char since code is
   // char-based...
   //
   for (i=0; i < argc; i++)
   {
	   UnicodeToAnsi(argv[i]);
   }


   //
   // Say Hello
   //
   TTCP_LogMsg( "PCAUSA Test TCP Utility V%s (IPv4/IPv6)\n", PCATTCP_VERSION );

   //
   // Start Winsock 2
   //
   if( WSAStartup( MAKEWORD(0x02,0x00), &g_WsaData ) == SOCKET_ERROR )
   {
      fprintf( stderr, Usage );
      TTCP_ExitTestOnWSAError( NULL, "WSAStartup" );
   }

   //
   // Fetch Command/Options For This Test
   //
   TTCP_SetConfigDefaults( &cmdBlock[0] );

   if( TTCP_ParseCommandLine( &cmdBlock[0], argc, argv ) != 0 )
   {
      return( 0 );
   }

   TTCP_LogMsg( "  IP Version  : %s\n",
      ( g_SockAddrFamily == AF_INET ? "IPv4" : "IPv6" )
      );

   //
   // Set The Ctrl-C Handler
   //
   // SetConsoleCtrlHandler( CtrlHandler, TRUE );

   if( cmdBlock[0].m_bOptMultiReceiver )
   {
      TTCP_LogMsg( "  Threading   : Multithreaded\n" );
   }

   //
   // Setup IP Addresses For Test
   //
   if( cmdBlock[0].m_bTransmit )
   {
      PADDRINFOA  pAddrInfo;

      // Setup Remote IP Address
      pAddrInfo = ResolveAddress(
                     argv[argc - 1],
                     "\0",                // Service Name
                     g_SockAddrFamily,
                     cmdBlock[0].m_Protocol == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM,
                     cmdBlock[0].m_Protocol
                     );

      if( !pAddrInfo )
      {
         TTCP_ExitTestOnWSAError( NULL, "Could not resolve destination" );
      }

      memcpy( &cmdBlock[0].m_RemoteAddr, pAddrInfo->ai_addr, pAddrInfo->ai_addrlen );

      SADDR_STORE_PORT( &cmdBlock[0].m_RemoteAddr ) = htons( cmdBlock[0].m_Port );

      cmdBlock[0].m_nRemoteAddrLen = SADDR_STORE_ADDR_LEN( &cmdBlock[0].m_RemoteAddr );

      freeaddrinfo( pAddrInfo );
   }

   // Set Periodic Timer Resolution
   // timeBeginPeriod( 1 );  // Set 1 millisecond timer resolution

   // Raise Thread Priority
   // SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL );

   for( i = 0; i < cmdBlock[0].m_NumberOfPorts; ++i )
   {
      if( i > 0 )
      {
         memcpy( &cmdBlock[i], &cmdBlock[0], sizeof( CMD_BLOCK ) );
         cmdBlock[i].m_Port = cmdBlock[i].m_Port + i;
      }

      //
      // Dispatch To Specialized Procedues
      //
      if( cmdBlock[i].m_bTransmit )
      {
         if( cmdBlock[i].m_Protocol == IPPROTO_UDP )
         {
            cmdBlock[i].m_ThreadHandle = CreateThread(
               NULL,
               0,
               (LPTHREAD_START_ROUTINE )TTCP_TransmitUDP,
               &cmdBlock[i],
               0,
               &cmdBlock[i].m_ThreadID
               );

            if( cmdBlock[i].m_ThreadHandle != NULL )
            {
               ThreadHandles[NumberOfThreads++] = cmdBlock[i].m_ThreadHandle;

               TTCP_LogMsg( "Started UDP Transmit Test %d...\n", i );
            }
            else
            {
               TTCP_LogMsg( "FAILED to Start UDP Transmit Test on Port %d\n", cmdBlock[i].m_Port );
            }
         }
         else
         {
            cmdBlock[i].m_ThreadHandle = CreateThread(
               NULL,
               0,
               (LPTHREAD_START_ROUTINE )TTCP_TransmitTCP,
               &cmdBlock[i],
               0,
               &cmdBlock[i].m_ThreadID
               );

            if( cmdBlock[i].m_ThreadHandle != NULL )
            {
               ThreadHandles[NumberOfThreads++] = cmdBlock[i].m_ThreadHandle;

               TTCP_LogMsg( "Started TCP Transmit Test %d...\n", i );
            }
            else
            {
               TTCP_LogMsg( "FAILED to Start TCP Transmit Test on Port %d\n", cmdBlock[i].m_Port );
            }
         }
      }
      else
      {
         if( cmdBlock[i].m_Protocol == IPPROTO_UDP )
         {
            cmdBlock[i].m_ThreadHandle = CreateThread(
               NULL,
               0,
               (LPTHREAD_START_ROUTINE )TTCP_ReceiveUDP,
               &cmdBlock[i],
               0,
               &cmdBlock[i].m_ThreadID
               );

            if( cmdBlock[i].m_ThreadHandle != NULL )
            {
               ThreadHandles[NumberOfThreads++] = cmdBlock[i].m_ThreadHandle;

               TTCP_LogMsg( "Started UDP Receive Test %d...\n", i );
            }
            else
            {
               TTCP_LogMsg( "FAILED to Start UDP Receive Test on Port %d\n", cmdBlock[i].m_Port );
            }
         }
         else
         {
            cmdBlock[i].m_ThreadHandle = CreateThread(
               NULL,
               0,
               (LPTHREAD_START_ROUTINE )TTCP_ListenTCP,
               &cmdBlock[i],
               0,
               &cmdBlock[i].m_ThreadID
               );

            if( cmdBlock[i].m_ThreadHandle != NULL )
            {
               ThreadHandles[NumberOfThreads++] = cmdBlock[i].m_ThreadHandle;

               TTCP_LogMsg( "Started TCP Receive Test %d...\n", i );
            }
            else
            {
               TTCP_LogMsg( "FAILED to Start TCP Receive Test on Port %d\n", cmdBlock[i].m_Port );
            }
         }
      }
   }
   for (i = 0; i < NumberOfThreads; i++)
   {
		nResult = WaitForSingleObject(ThreadHandles[i],INFINITE);
		if (nResult == WAIT_FAILED)
		{
			TTCP_LogMsg( "FAILED to wait for thread exit. Error 0x%x\n", GetLastError());
		}
   }

   //while( g_bShutdown == FALSE )
   //{
   //    Sleep( 500 );
   //}

   // Clear Periodic Timer Resolution
   // timeEndPeriod( 1 );  // Clear 1 millisecond periodic timer resolution

   WSACleanup();

   return( 0 );
}

/*
 *          N R E A D
 */
int TTCP_Nread( PTEST_BLOCK pTBlk, int count )
{
   int len = sizeof(SOCKADDR_STORAGE);
   register int cnt;

   if( pTBlk->m_Cmd.m_Protocol == IPPROTO_UDP )
   {
        cnt = recvfrom( pTBlk->m_Socket_fd,
               pTBlk->m_pBuf, count, 0,
               (PSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr,
               &len
               );

        pTBlk->m_numCalls++;
   }
   else
   {
        if( b_flag )
      {
         cnt = TTCP_mread( pTBlk, count );  /* fill buf */
      }
      else
      {
         cnt = recv( pTBlk->m_Socket_fd, pTBlk->m_pBuf, count, 0 );

         if( cnt == SOCKET_ERROR )
         {
            int nError = WSAGetLastError();
         }

         pTBlk->m_numCalls++;
      }
    }

    if( pTBlk->m_Cmd.m_bTouchRecvData && cnt > 0 )
   {
        register int c = cnt, sum;
        register char *b = pTBlk->m_pBuf;
      sum = 0;
        while (c--)
            sum += *b++;
    }

    return(cnt);
}

/*
 *          N W R I T E
 */
int TTCP_Nwrite( PTEST_BLOCK pTBlk, int count )
{
   register int cnt;

   //
   // Introduce Write Delay After First Write Call
   //
   if( pTBlk->m_Cmd.m_nOptWriteDelay && pTBlk->m_numCalls > 0 )
   {
      Sleep( pTBlk->m_Cmd.m_nOptWriteDelay );
   }

   if( pTBlk->m_Cmd.m_Protocol == IPPROTO_UDP )
   {
again:
      cnt = sendto( pTBlk->m_Socket_fd, pTBlk->m_pBuf, count, 0,
               (PSOCKADDR )&pTBlk->m_Cmd.m_RemoteAddr,
               pTBlk->m_Cmd.m_nRemoteAddrLen
               );

      pTBlk->m_numCalls++;

      if( cnt == SOCKET_ERROR && WSAGetLastError() == WSAENOBUFS )
      {
         delay(18000);
         SetLastError(ERROR_SUCCESS);
         goto again;
      }
   }
   else
   {
      cnt = send( pTBlk->m_Socket_fd, pTBlk->m_pBuf, count, 0 );
      pTBlk->m_numCalls++;
   }

   return(cnt);
}

void delay( int us )
{
    struct timeval tv;

    tv.tv_sec = 0;
    tv.tv_usec = us;
    select( 1, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv );
}

/*
 *          M R E A D
 *
 * This function performs the function of a read(II) but will
 * call read(II) multiple times in order to get the requested
 * number of characters.  This can be necessary because
 * network connections don't deliver data with the same
 * grouping as it is written with.  Written by Robert S. Miles, BRL.
 */
int TTCP_mread( PTEST_BLOCK pTBlk, unsigned n)
{
   char *bufp = pTBlk->m_pBuf;
   register unsigned    count = 0;
   register int     nread;

   do
   {
        nread = recv( pTBlk->m_Socket_fd, bufp, n-count, 0);

        pTBlk->m_numCalls++;

        if(nread < 0)  {
            //perror("ttcp_mread");
            return(-1);
        }
        if(nread == 0)
            return((int)count);

        count += (unsigned)nread;

        bufp += nread;
     }
      while(count < n);

    return((int)count);
}

#define END(x)  {while(*x) x++;}

char *outfmt( double b )
{
   static char obuf[50];

   switch (fmt) {
    case 'G':
        sprintf_s(obuf, sizeof( obuf ), "%.2f GB", b / 1024.0 / 1024.0 / 1024.0);
        break;
    default:
    case 'K':
        sprintf_s(obuf, sizeof( obuf ), "%.2f KB", b / 1024.0);
        break;
    case 'M':
        sprintf_s(obuf, sizeof( obuf ), "%.2f MB", b / 1024.0 / 1024.0);
        break;
    case 'g':
        sprintf_s(obuf, sizeof( obuf ), "%.2f Gbit", b * 8.0 / 1024.0 / 1024.0 / 1024.0);
        break;
    case 'k':
        sprintf_s(obuf, sizeof( obuf ), "%.2f Kbit", b * 8.0 / 1024.0);
        break;
    case 'm':
        sprintf_s(obuf, sizeof( obuf ), "%.2f Mbit", b * 8.0 / 1024.0 / 1024.0);
        break;
    }

    return obuf;
}

